package ija.net;

import ija.controller.CheckersController;
import ija.controller.CheckersController.Accept;
import ija.controller.CheckersController.Deny;
import ija.controller.CheckersController.ExitGame;
import ija.controller.CheckersController.Move;
import ija.controller.CheckersController.SetPlayer;
import ija.controller.CheckersController.StartGame;
import ija.model.players.Player;
import ija.xls.notation.NotationReader;
import ija.xls.notation.NotationReaderFactory;
import ija.xls.xml.game.XmlGameReaderWriter;

import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Iterator;

import org.dom4j.DocumentException;

/**
 * Networker umoznuje hrat hru po siti. Je zodpovedny za sitovou komunikaci.
 * Umoznuje zasilani a zpracovani zprav, ktere jsou spolecne jak pro
 * inicializacni tak pro pasivni stranu peer-to-peer komunikace. Dedi od nej
 * tridy Initiator a Listener.
 * 
 * @author Radek Necas, xnecas18
 */
public class Networker implements ServerEventListener {

    public final int DEFAULT_PORT = 55555;

    // Klient a server cast
    protected Server server;
    protected Client client;

    // Prace s xml casti zpravy
    protected XmlGameReaderWriter xml;
    protected NotationReader notation;

    protected CheckersController controller;

    // Listeneri
    protected Move moveListener;
    protected SetPlayer setPlayerListener;

    /**
     * Konstruktor vytvori networker a nainicializuje jeho polozky
     * 
     * @param controller
     *            kontroler hry dama
     */
    public Networker(CheckersController controller) {
        this.server = null;
        this.client = null;
        this.xml = new XmlGameReaderWriter();
        this.controller = controller;
        this.notation = controller.getNotationReader();

        this.moveListener = controller.createMoveListener();
        this.setPlayerListener = controller.createSetPlayerListener();

    }

    public void setClient(String remoteIp, int remotePort) throws UnknownHostException {
        this.client = new Client(remoteIp, remotePort);
    }

    // //////////////////////////////////////////////////////////////////////////
    // ////////////////// ZASILANI ZPRAV
    // ////////////////////////////////////////
    // //////////////////////////////////////////////////////////////////////////
    // region SEND MESSAGE

    /**
     * Metoda zasila zpravu ACCEPT
     * 
     * @throws IOException
     */
    public void sendAccept() throws IOException {
        if (this.client == null) {
            throw new IOException("Client not created");
        }

        this.client.send(new Message(MessageType.ACCEPT, null));
    }

    /**
     * Metoda pro zaslani zpravy DENY
     * 
     * @throws IOException
     */
    public void sendDeny() throws IOException {
        if (this.client == null) {
            throw new IOException("Client not created");
        }

        this.client.send(new Message(MessageType.DENY, null));
    }

    /**
     * Metoda pro zaslani zpravy PLAYERINFO
     * 
     * @param p
     *            hrac, jehoz informace se budou zasilat
     * @throws IOException
     */
    public void sendPlayerInfo(Player p) throws IOException {
        this.xml.initOutput(NotationReaderFactory.NotationType.IJA_NOTATION);
        this.xml.addPlayerElement(0, p);
        this.client.send(new Message(MessageType.PLAYERINFO, this.xml.outputAsXml()));
        this.xml.clearOutput();
    }

    /**
     * Metoda pro zaslani zpravy GAMEMOVE
     * 
     * @param gameMove
     *            tah, ktery se bude zasilat
     * @throws IOException
     */
    public void sendGameMove(String gameMove) throws IOException {
        if (this.client == null) {
            throw new IOException("Client not created");
        }

        System.out.println("Networker zasila GameMove: " + gameMove.toString());
        this.client.send(new Message(MessageType.GAMEMOVE, gameMove));
    }

    /**
     * Metoda pro zaslani zpravy ENDGAME
     * 
     * @param reason
     *            duvod ukonceni hry (konec hry, vzdani se, ...)
     * @throws IOException
     */
    public void sendEndGame(String reason) throws IOException {
        if (this.client == null) {
            throw new IOException("Client not created");
        }

        this.client.send(new Message(MessageType.ENDGAME, reason));
    }

    /**
     * Metoda zasle zpravu, ktera ukonci server
     * 
     * @throws IOException
     * @throws UnknownHostException
     */
    public void closeServer() throws IOException, UnknownHostException {
        Client endClient = new Client("localhost", this.server.serverSocket.getLocalPort());
        endClient.send(new Message(MessageType.ENDSERVER, null));
    }

    // endregion SEND MESSAGE
    // //////////////////////////////////////////////////////////////////////////
    // //////////////// ZPRACOVANI UDALOSTI OD SERVERU
    // //////////////////////////
    // //////////////////////////////////////////////////////////////////////////
    // region SERVE MESSAGE

    /**
     * Metoda zpracovava vsechny udalosti od serveru a transformuje je na
     * udalosti pro kontroler (stejny prototyp udalosti jako z GUI).
     * 
     * @param msg
     *            prijata zprava
     */
    @Override
    public void serveMessage(Message msg) {
        switch (msg.type) {
        case ACCEPT:
            this.fireAcceptConnection();
            break;

        case DENY:
            this.fireDenyConnection();
            break;

        case PLAYERINFO:
            this.firePlayerInfo(msg);
            break;

        case GAMEMOVE:
            this.fireGameMove(msg);
            break;

        case ENDGAME:
            this.fireEndGame(msg);
            break;
            
        case SERVERERR:
            this.fireServerErr(msg);
            break;
        }
    }

    /**
     * Metoda zasle kontroleru udalost ACCEPT
     */
    protected void fireAcceptConnection() {
        System.out.println("Networker zpracovava ACCEPT");
        Accept al = this.controller.createAcceptListener();
        al.ActionPerformed(new ActionEvent(this, 0, null));
    }

    protected void fireDenyConnection() {
        Deny denyListener = this.controller.createDenyListener();
        denyListener.ActionPerformed(new ActionEvent(this, 0, null));
    }

    /**
     * Metoda zasle kontroleru udalost STARTGAME
     */
    protected void fireStartGame() {
        StartGame startGameListener = this.controller.createStartGameListener();
        startGameListener.actionPerformed(new ActionEvent(this, 1, null));
    }

    /**
     * Metoda zasle kontroleru udalost MOVE
     * 
     * @param msg
     */
    protected void fireGameMove(Message msg) {
        System.out.println("Networker preposila GameMove: " + msg.getDetails());
        this.moveListener.actionPerformed(new ActionEvent(this, 1, msg.getDetails()));
    }

    /**
     * Metoda zasle kontroleru zpravu PLAYERINFO s informacemi o hraci
     * 
     * @param msg
     */
    protected void firePlayerInfo(Message msg) {
        try {
            this.xml.initInputFromString(msg.getDetails());
        } catch (DocumentException ex) {
            // ----------------------- ZASLE SE EVENT KONTROLLERU
            // -----------------------------
            System.err.println("Chyba pri zpracovani playerinfo: " + ex.getLocalizedMessage());
        }

        Iterator<Player> it = this.xml.playersIterator();
        Player p;
        if (it.hasNext()) {
            p = it.next();
            this.setPlayerListener.onPlayerChanged(null, p, -1);
        }
        this.xml.clearInput();
    }

    protected void fireEndGame(Message msg) {
        ExitGame exitGameListener = this.controller.createExitGameListener();
        exitGameListener.actionPerformed(new ActionEvent(this, 0, msg.getDetails()));

    }

    protected void fireServerErr(Message msg) {
        
    }
    
    // endregion SERVE MESSAGE
}
